1 前言
其实 Spring 的基本思想就是“万物都是 bean”,那么为了满足 spring 工程的需要,spring 中有一些默认的 bean 选项,它们用于处理请求,渲染视图等。比如上一篇文章就用过的 viewResolver 的配置。当然,servlet 也允许你配置使用不同特定的 bean,但是,如果你没有配置,spring 将会按照默认的 bean 进行配置。本章将会详细说明文档中列出的 bean 的配置以及具体的使用例子,所讲述的 bean 类型包括:(使用版本:Spring-webmvc 4.3.18.RELEASE)
HandlerMapping
和 HandlerAdapterHandlerExceptionResolver
LocaleResolver
&LocaleContextResolver
ThemeResolver
MultipartResolver
2 HandlerAdapter 和 HandlerMapping 解析
2.1 前期准备
本章节将基于文档实践(一)的代码进行后续的操作,因此我们使用了单个 ContextConfig 来配置工程 Context 对象,也就是 root-context.xml 文件。另一方面,为了实现 HandlerMapping 在 xml 配置的功能,我们关掉了
1 | <mvc:annotation-driven/> |
的功能,使得 @Controller 注解下的类不再会被自动配置并且做 url 的映射,现在再去试一下 localhost:8080/hello.do 的话,已经是 404 Not Found 了。之后再进行后续的实践过程。
这里 HandlerMapping 和 HandlerAdapter 一起讲是因为,HandlerMapping 需要 HandlerAdapter 的支持才能正常运行。HandlerMapping 用于将请求的 url 映射到对应的 controller 上面,如果没有进行配置的话,@Controller 注解即为 HandlerMapping,上一篇的 ExampleController 即有着和上述相似的功能。值得注意的是,Spring MVC 4.0 之后主推 Annotation Driven,也就是注解驱动模式下的工程,因此,对应的 adapter 已经标记为 deprecated,不推荐使用,这里只做帮助理解使用。
2.2 HandlerAdapter
由于工程中的 Controller 都是用注解配置的,因此,在 DispatcherServlet 根据 bean 的配置信息(root-context.xml,我们用 Context 对象来配置 bean 的信息)知道了自己所需要调用的 controller 之后,他需要根据注解来提取其他的所需要的信息。这时候就需要 HandlerAdapter 来做这些解析的事情。
然而,目前的 Spring MVC 的配置都基于注解,因此,HandlerAdapter 也退居幕后,@Controller 注解包含了其中逻辑,在 Annotation-driven 被我们关掉的场景下,也只要做好 HandlerMapping,就可以成功地映射你想要的 url
2.3 HandlerMapping
HandlerMapping 本质还是一个 Bean,他在 Spring MVC 装配完成之后,执行着将 URL 的请求转发到对应的 Controller 执行后续视图,数据等返回的工作。因此,在配置 HandlerMapping Bean 的时候,需要配置 property 的 mappings 字段,并且在
1 | <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> |
2.4 HandlerAdapter 和 HandlerMapping 的测试
为了同步一下,目前 root-context.xml (Spring Context 对象配置文件) 的配置加入了 HandlerMapping 的配置:
1 |
|
并且新增了 HandlerMappingController.java 的配置:
1 | package com.test.myapp.example.handlermapping; |
我们看到,HandlerMapping 下面配置了 /handler-mapping.do 的映射。因此,在运行工程之后,输入 localhost:8080/handler-mapping.do,就可以看到对应的 handler_mapping_hello.jsp 上的前端视图返回。
3 HandlerExceptionResolver 解析
HandlerExceptionResolver 是工程中用于捕获特定 Exception 的 Bean,可以提前设定自己需要捕获并且定向的 Exception,并且交由 HandlerExceptionResolver 映射到特定的视图页上面。 目前常用的方法有:
- 实现 HandlerExceptionResolver 接口
- 在方法上使用 @ExceptionHandler 注解
3.1 实现 HandlerExceptionResolver 接口
HandlerExceptionResolver 接口只有一个待实现的方法
1 | ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4); |
为了工程上面比较直观简便的实现,我们只需要做最简单的实现:拿到 Exception 的具体类,并且返回对应的 error 的视图,并且记录下 Exception 的 message,显示在视图页面上面。因此我们的工序如下:
3.1.1 实现一个自定义的 Exception: MyCustomException
1 | package com.test.myapp.example.handlermapping; |
这个 Exception 类很简单,只是把 message 放进 Exception 中,无需赘述,主要是要让 ExceptionResolver 捕获该 Exception。
3.1.2 实现 HandlerExceptionResolver 接口:ExceptionResolver
1 | package com.test.myapp.example.handlermapping; |
我们使用 ExceptionResolver 实现了 resolveException 方法,并且会解析 MyCustomException 并且在 ModelAndView 对象加入一个变量,并且返回名为 “error” 的 jsp 视图。我们也可以在 error.jsp 上显示这个 msg 字段的信息。
3.1.3 HandlerMappingController 添加两个会抛出 Exception 的接口
为了对照效果,我们实现两个接口,一个会抛出 MyCustomException,另一个则会抛出普通的 IllegalArgumentException,而我们需要捕获的则是 MyCustomException。
1 | package com.test.myapp.example.handlermapping; |
3.1.4 视图文件 error.jsp 配置
视图文件 error.jsp 比较简单,只要体现 msg 字段即可:
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
3.1.5 测试
运行工程后,在浏览器分别输入:
- http://localhost:8080/custom-exception.do:浏览器返回了 error.jsp 的视图并且输出了包裹在 MyCustomException 的信息,符合预期
- http://localhost:8080/argument-exception.do:浏览器返回了 500 Internal Server Error,因为没有用于 IllegalArgumentException 的 resolver,因此返回了默认的视图。
3.2 使用 @ExceptionHandler 注解
另一种方法是使用 @ExceptionHandler 的注解,该注解用于 method 的签名上面,我们可以实现一个 Controller 的基类并让实际接收 url 请求的 Controller 继承该基类。值得注意的是,这个方法实现的 ExceptionResolver 只会在该 Controller 内部有效,而来自其他 Controller 类的 Exception 则无法得到解析。具体代码步骤如下:
3.2.1 设置自定义 Exception: CustomExceptionForAnnotation
我们为这一次测试也设置了自定义的 Exception 类,实现方法也很简单,可以自定义 Exception 中的信息:
1 | package com.test.myapp.example.exceptionresolver; |
3.2.2 实现有 @ExceptionHandler 注解的 Controller 基类
我们的 Controller 基类需要 Resolve CustomExceptionForAnnotation,需要用 @ExceptionHandler(CustomExceptionForAnnotation.class) 进行配置,具体方法如下:
1 | package com.test.myapp.example.exceptionresolver; |
可以看到,该类中所含有的方法仅会解析 CustomExceptionForAnnotation 类,并且将其重新导向 error.jsp 视图,最后输出对应的 message 信息到前端。
3.2.3 实现两个 Controller 类
为了使测试结果有对照性,我们实现了两个 Controller 类,一个继承自 BaseExceptionResolver,另一个则没有。理论上说,继承了 BaseExceptionResolver 的 Controller 将可以解析上面的 Exception,而另一个则不能。具体的配置方法如下:
继承了 BaseExceptionResolver 的 Controller 类
1
2
3
4
5
6
7
8
9
10
11
12
13package com.test.myapp.example.exceptionresolver;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
public class MyExceptionController extends BaseExceptionResolver {
"exception-for-annotation.do") (
public void exceptionForAnnotation() {
throw new CustomExceptionForAnnotation("Oooops, you get CustomExceptionForAnnotation message");
}
}未继承 BaseExceptionResolver:
1
2
3
4
5
6
7
8
9
10
11
12
13package com.test.myapp.example.exceptionresolver;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
public class MyExceptionOutsideController {
"exception-for-annotation-outside.do") (
public void exceptionForAnnotation() {
throw new CustomExceptionForAnnotation("Oooops, you get CustomExceptionForAnnotation message");
}
}
3.2.4 测试
我们仍然使用了 error.jsp 视图来做最后的测试工作,我们看到 BaseExceptionResolver 在捕获异常后,仍然会输出 error.jsp 的视图。我们将会请求两个具体 Controller 类的 url,观察是否会有我们想要的视图的输出:
- localhost:8080/exception-for-annotation.do: 成功输出了我们放入 CustomExceptionForAnnotation 的信息。
- localhost:8080/exception-for-annotation-outside.do: 页面输出了 500 的错误信息,并且带上了 Exception 中的信息,因为其没有继承 BaseExceptionResolver,因此也没有对应的 Exception 解析器了。
4 小结
本章主要讲述了 HandlerMapping 和 HandlerExceptionResolver 的具体实现代码,一个是处理正常的 url 请求的映射工具,而另一个则是专门处理工程在运行过程中出现 Exception 的处理方法。下一次我将继续介绍后面这几个特殊 Bean 的用法。